#!/bin/bash

[[ $DEBUG ]] && {
    set -x
    export PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): '
    exec 2>/var/log/smoketest.log
}

if [[ ! -f /opt/dell/.all_nets ]]; then
    touch /opt/dell/.all_nets
    for n in "public host" "storage host" "nova_fixed router"; do
        crowbar network allocate_ip default $(hostname --fqdn) $n
    done

    /opt/dell/bin/blocking_chef_client.sh
fi


found_groups=()
export LOGDIR=/var/log/smoketest
if [[ -f /etc/crowbar.install.key ]]; then
    read CROWBAR_KEY </etc/crowbar.install.key
    export CROWBAR_KEY
else
    die "Cannot find crowbar key!"
fi
export PATH="/opt/dell/bin:$PATH"
set -o pipefail
mkdir -p "$LOGDIR"

die() { for l in "$@"; do echo "$(date '+%F %T %z'): $l"; done; exit 1; }

[[ -d /opt/dell/barclamps ]] || die "Cannot find barclamps!"
[[ $1 && -d /opt/dell/barclamps/$1 ]] || \
    die "$1 is not the name of a barclamp." \
    "If you don't want to test any particular barclamp, please pass crowbar as \$1."

is_in() (
    m="$1"
    shift
    for i in "$@"; do
        [[ $m != $i ]] && continue
        return 0
    done
    return 1
)

is_barclamp() { [[ -f /opt/dell/barclamps/$1/crowbar.yml ]]; }

match_keys() {
    # $@ = regexes that we should try and match on.
    local line='' re=''
    while read line; do
        for re in "$@"; do
            [[ $line =~ ^[a-zA-Z_0-9]+\[\'$re[^\']*\'\]=\$\'(.*) ]] || continue
            echo "${BASH_REMATCH[1]%\'}"
            continue 2
        done
    done
}

find_group_members() {
    group=${1#@}
    local key line
    is_in "$group" "${found_groups[@]}" && return
    for bc in /opt/dell/barclamps/*; do
        is_barclamp "${bc##*/}" || continue
        while read line; do
            is_in "$group" "$line" || continue 2
        done < <(parse_yml_or_json "$bc/crowbar.yml" res | \
            match_keys 'barclamp\.member')
        echo ${bc##*/}
    done
    found_groups+=("$group")
}

barclamp_group_expand() {
    for bc in "$@"; do
        if [[ $bc = @* ]]; then
            find_group_members "${bc#@}"
        else
            echo "$bc"
        fi
    done
}

__barclamp_deps() {
    # If we are already in $res, then we don't need to be discovered again.
    is_in "$1" "${res[@]}" && return 0
    local bc line
    unset barclamp_requires smoketest_requires
    is_barclamp "$1" || return 1
    local -a bc_array
    while read line; do
        bc_array+=($(barclamp_group_expand "${line}"))
    done < <(parse_yml_or_json "/opt/dell/barclamps/$1/crowbar.yml" bc_res | \
        match_keys 'barclamp\.requires' 'smoketest\.requires')
    for bc in "${bc_array[@]}"; do
        __barclamp_deps "$bc"
    done
    is_in "$1" "${res[@]}" || res+=("$1")
}

# Get all the dependencies for a barclamp in sorted order.
barclamp_deps() {
    is_barclamp "$1" || die "$1 is not a barclamp!"
    local res=(crowbar)
    __barclamp_deps "$1" || die "Could not find dependencies for $1"
    echo "${res[@]}"
}

wait_for_ready() {
    local all_ready=false
    local nodes=() state=""
    local ready_re='ready\.$'
    local deadline=$(($(date '+%s') + 2400))
    echo "$(date '+%F %T %z'): Waiting 2400 seconds for all nodes to become ready."
    while [[ $all_ready != true ]] && (($(date '+%s') < $deadline)) ; do
        all_ready=true
        for node in $(knife node list); do
            state="$(check_ready "$node")"
            [[ $state =~ [Dd]iscovered ]] && \
                crowbar machines allocate "$node" >&/dev/null
            [[ $state =~ problem ]] && \
                die "$node transitioned into problem state." \
                "Smoketests failed."
            [[ $state =~ $ready_re ]] || all_ready=false
        done
        [[ $all_ready = true ]] || sleep 10
    done
    if [[ $all_ready != true ]]; then
        echo "Some nodes failed to transistion to ready."
        for node in $(knife node list); do
            echo "$(date '+%F %T %z'): $(check_ready "$node")"
        done
        exit 1
    else
        echo "$(date '+%F %T %z'): All nodes ready."
    fi
}

barclamp_deployed() {
    # $1 = barclamp to check for deployed proposals
    [[ $(crowbar $1 list) != 'No current configurations' ]]
}

deploy_barclamp() {
    # $1 = barclamp to deploy proposal for
    echo "$(date '+%F %T %z'): Creating smoketest proposal for $1"
    crowbar "$1" proposal create smoketest >&/dev/null || \
        die "Could not create smoketest proposal for $1"
    crowbar "$1" proposal show smoketest > \
        "$LOGDIR/$1-default.json" || \
        die "Could not show smoketest proposal for $1"
    if [[ -x $bc_dir/smoketest/modify-json ]]; then
            echo "$(date '+%F %T %z'): Editing smoketest proposal for $1"
        "$bc_dir/smoketest/modify-json" < \
            "$LOGDIR/$1-default.json" > \
            "$LOGDIR/$1-modified.json" || \
            die "Failure editing smoketest proposal for $1"
            crowbar "$1" --file "$LOGDIR/$1-modified.json" \
                proposal edit smoketest || \
                die "Failed to upload modified smoketest proposal for $1"
    fi
    echo "$(date '+%F %T %z'): Committing smoketest proposal for $1"
    crowbar "$1" proposal commit smoketest || \
        die "Failed to commit smoketest proposal for $1"
    echo "$(date '+%F %T %z'): Smoketest proposal for $1 committed"
    crowbar "$1" show smoketest >"$LOGDIR/$1-deployed.json"
    return 0
}

# run hooks.  They will be sorted in lexicographic order,
# so naming them with numeric prefixes indicating the order
# they should run in is a Good Idea.
run_hooks() {
    # $1 = name of the test
    # $2 = path to find the hooks in
    # $3 = Timeout for the tests, defaults to 300 seconds.
    # $4 = Extension for the hooks, defaults to 'hook'
    local test_name="$1" test_dir="$2"
    local deadline=$(($(date '+%s') + ${3})) hook="" status=""
    echo "$(date '+%F %T %z'): Running smoketests for $bc."
    echo "Timed Out" > "$LOGDIR/$test_name.test"
    (   sleep 1
        unset http_proxy https_proxy
        for hook in "$test_dir"/*.test; do
            [[ -x $hook ]] || {
                echo "$(date '+%F %T %z'): $hook not executable, skipping."
                continue
            }
            echo "$(date '+%F %T %z'): Running test hook ${hook##*/}: "
            "$hook" && {
                echo "$(date '+%F %T %z'): Test hook ${hook##*/} passed."
                continue
            }
            echo "$(date '+%F %T %z'): Test hook ${hook##*/} failed."
            echo "Failed" >"$LOGDIR/$test_name.test"
            exit
        done
        echo "Passed" >"$LOGDIR/$test_name.test"
        exit
    ) &
    local testpid=$!
    (   cd /proc/$testpid
        while [[ -f cmdline ]] && (($(date '+%s') <= $deadline)); do
            sleep 10
        done)
    status=$(cat "$LOGDIR/$test_name.test")
    echo "$(date '+%F %T %z'): $bc smoketests $status."
    case $status in
        Passed) return 0;;
        Failed) return 1;;
        *)  # We timed out.  Kill everything associated with this test.
            kill -TERM "$testpid"
            return 1;;
    esac
}

wait_for_ready

for bc in $(barclamp_deps "$1"); do
    bc_dir="/opt/dell/barclamps/$bc"
    if ! barclamp_deployed "$bc"; then
        echo "$(date '+%F %T %z'): Deploying $bc."
        deploy_barclamp "$bc" || exit 1
    fi
    if [[ -d $bc_dir/smoketest ]]; then
        smoketest_timeout=$(parse_yml_or_json \
            $bc_dir/crowbar.yml res |match_keys 'smoketest\.timeout')
        run_hooks "$bc" "$bc_dir/smoketest" \
            "${smoketest_timeout:-300}" test 2>&1 | \
            tee "$LOGDIR/$bc-smoketest.log"  || exit 1
    fi
done
